package org.example.utils;

import com.google.common.collect.ImmutableMap;
import io.jsonwebtoken.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.logging.log4j.Logger;
import org.example.config.AppProperties;
import org.springframework.stereotype.Component;
import javax.crypto.spec.SecretKeySpec;
import java.security.Key;
import java.util.Base64;
import java.util.Date;
import java.util.List;

import io.jsonwebtoken.security.SignatureException;

@Component
public class JwtUtils {

    private Log log = LogFactory.getLog(JwtUtils.class);

    private Key key;
    private long expire;

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class JwtVerifyInfo {
        private JwtStatus jwtStatus;
        private Claims claims;
    }

    // 定义了一个内部的枚举，
    public enum JwtStatus {
        /**
         * 1.correct 正确
         * 2.expired 过期
         * 3.temper 篡改
         */
        correct, expired, temper;
    }

    /**
     * 1.如果一个可以被纳入到IOC容器的Bean, 需要的类的实例在IOC
     *  容器中，会自动的注入进来，是按照类型注入的
     * 2.JWTUtils头顶加了一个 @Component, 所以它的实例会被纳入到IOC容器，那么就需要对
     *  JwtUtils进行实例化(需要调用构造方法)，又由于 JwtUtils只有唯一的一个有参构造方法，
     *  就会按照 @Autowired的方式到容器中查找对应的Bean, 然后按照构造器注入的方式
     *  注入进来。
     */
    public JwtUtils(AppProperties appProperties) {
        this.key = new SecretKeySpec(Base64.getDecoder().decode(appProperties.getJwt().getKey()), "HmacSHA512");
        this.expire = appProperties.getJwt().getExpireTime();
    }

    /**
     * 生成一个JWT, 传入当前用户的用户名以及其权限列表
     * @param subject
     * @return
     */
    public String generateJwt(String subject, List<String> perms) {
        // 生成一个 JWT的字符串
        String jwt = Jwts.builder()
                .setSubject(subject)  // 用户名信息，要放到的 payload
                .addClaims(ImmutableMap.of("perms", perms))  // 在jwt中携带额外的数据， 将来在 JwtFilter中去取
                .setIssuedAt(new Date())  // jwt的颁发日期
                .setExpiration(new Date(System.currentTimeMillis() + expire))   // jwt的有效期
                .signWith(key, SignatureAlgorithm.HS512)  // 生成jwt的算法
                .compact();  // 返回一个 jwt的字符串

        return jwt;
    }

    /**
     * 生成一个JWT, 传入当前用户的用户名
     * @param subject
     * @return
     */
    public String generateJwt(String subject) {
        // 生成一个 JWT的字符串
        String jwt = Jwts.builder()
                .setSubject(subject)  // 用户名信息，要放到的 payload
                .setIssuedAt(new Date())  // jwt的颁发日期
                .setExpiration(new Date(System.currentTimeMillis() + expire))   // jwt的有效期
                .signWith(key, SignatureAlgorithm.HS512)  // 生成jwt的算法
                .compact();  // 返回一个 jwt的字符串

        return jwt;
    }

    // 该方法是验证JWT的合法性
    public JwtVerifyInfo verifyJwt(String jwt) {
        JwtVerifyInfo verifyInfo = new JwtVerifyInfo();
        try {
            /**
             * 验证JWT, 如果不正确，会抛出三种异常：
             *    1) ExpiredJwtException: 表示 JWT过期了；
             *    2) MalformedJwtException: 表示JWT的格式不对;
             *    3) SignatureException: 表示验签失败。
             * 验证JWT的合法性，验证状态有三种：
             *   1.正确
             *   2.过期
             *   3.被篡改
             *
             * claims, 可以获取到用户名。
             */
            Claims claims = Jwts.parserBuilder()
                    .setSigningKey(key).build()
                    .parseClaimsJws(jwt).getBody();
            verifyInfo.setClaims(claims);
            verifyInfo.setJwtStatus(JwtStatus.correct);
        }catch (ExpiredJwtException ex) { // 过期了
            log.warn("jwt过期了");
            verifyInfo.setJwtStatus(JwtStatus.expired);
        }catch (MalformedJwtException | SignatureException ex) {  // 都表示JWT被改过
            log.warn("jwt格式不对");
            verifyInfo.setJwtStatus(JwtStatus.temper);
        }
        return verifyInfo;
    }
}
